import model


class UmlTransform:
    """
    UML Model 转换。
    """

    partial_classes = {
        "MultiplicityElement": {},
        "ParameterableElement": {},
        "Relationship": {},
        "TemplateableElement": {},
        "TemplateSignature": {},
        "ActivityGroup": {
            "srcParent": "NamedElement",
        },
        "DeployedArtifact": {
            "srcParent": "NamedElement",
        },
        "DeploymentTarget": {
            "srcParent": "NamedElement",
        },
        "InteractionFragment": {
            "srcParent": "NamedElement",
        },
        "MessageEnd": {
            "srcParent": "NamedElement",
        },
        "Namespace": {
            "srcParent": "NamedElement",
        },
        "RedefinableElement": {
            "srcParent": "NamedElement",
        },
        "TypedElement": {
            "srcParent": "NamedElement",
        },
        "Vertex": {
            "srcParent": "NamedElement",
        },
        "Association": {
            "srcParent": "Classifier",
            "newParents": ["Relationship"],
        },
        "BehavioredClassifier": {
            "srcParent": "Classifier",
        },
        "StructuredClassifier": {
            "srcParent": "Classifier",
        },
        "EncapsulatedClassifier": {
            "srcParent": "Classifier",
            "newParents": ["StructuredClassifier"],
        },
        "ConnectableElement": {
            "newParents": ["ParameterableElement","TypedElement"]
        },
        "UMLBehaviorDiagram": {
            "srcParent": "UMLDiagram",
        },
    }

    __parent_order = ["UMLDiagram", "Classifier", "NamedElement", "Element"]

    __rule_replace_parent_classes = [
        "UMLEdge",
        "UMLShape",
        "UMLStyle",
        "UMLDiagram",
        "UMLDiagramElement",
    ]

    href_types = {
        "http://www.omg.org/spec/UML/20131001/UML.xmi#": "../uml/",
        "http://www.omg.org/spec/DD/20131001/DI.xmi#": "../di/",
        "http://www.omg.org/spec/DD/20131001/DC.xmi#": "../dc/",
    }

    base_element_classes = {"Element", "UMLDiagramElement"}
    base_class = "BaseElement"
    json_class = "JsonSerializable"
    
    def exec(self, mod: model.UmlModel):
        """
        - UMLDI 嵌入父类的属性
        - 注释类不要继承Element
        - 多重继承变成单一继承


        Args:
            mod (model.UmlModel): UML 模型
        """
        self.model = mod
        self.packages: dict[str, model.UmlPackage] = {}
        self.classes: dict[str, model.UmlClass] = {}
        self.enums: dict[str, model.UmlClass] = {}
        self.__prepare()
        self.__rule_update_umldi()
        self.__rule_comment()
        self.__rule_replace_parent()
        self.__rule_partial_interface()
        self.__calc_is_partial_element()

    def __prepare(self):
        # 区分Package
        for package in self.model.packages:
            self.packages[package.name] = package

        # 类Map
        for package in self.model.packages:
            self.__prepare_package(package)

    def __prepare_class(self, cls: model.UmlClass):
        self.classes[cls.name] = cls

    def __prepare_package(self, package: model.UmlPackage):
        for element in package.packagedElements.values():
            if (
                element.xmi_type == model.UmlXmiInfo.type_class
                or element.xmi_type == model.UmlXmiInfo.type_data_type
            ):
                self.__prepare_class(element)

            elif element.xmi_type == model.UmlXmiInfo.type_package:
                self.__prepare_package(element)

    def __rule_comment(self):
        self.classes["Comment"].generalizations = []

    def __rule_update_umldi(self):
        cls = self.classes["UMLDiagram"]
        for index, general in enumerate(cls.generalizations):
            type_name = self.convert_href_type(general.general)
            if type_name == "PackageableElement":
                cls.generalizations.pop(index)
                break

    def __rule_partial_interface(self):
        for name in UmlTransform.partial_classes.keys():
            self.__rule_partial_interface_each(name)

        # - 处理继承者
        self.__rule_partial_update_children()

    def __rule_partial_interface_each(self, name: str):
        """
        将类转变成 元素部分的形式
        - 本类: 删掉继承的类
        - 本类: 继承PartialElement(owner, json)
        - 本类: 构造器
        - 本类: 定义成员（不变）
        - 子类: 构造器传入子类this
        - 引用者: 不变
        Args:
            name (str): class name
        """
        cls: model.UmlClass = self.classes[name]

        # - 处理本类
        self.__gen_partial(cls)

    def __gen_partial(self, cls: model.UmlClass):
        """
        清除本类的父类
        让本类继承 PartialElement

        Args:
            cls (model.UmlClass): _description_
        """
        parents = UmlTransform.partial_classes[cls.name].get("newParents", ["PartialElement"])

        cls.generalizations.clear()
        for parent in parents:
            generalization = model.UmlGeneralization()
            generalization.general = parent
            cls.generalizations.append(generalization)
        cls.baseClass = parents[0]
        cls.isPartialElement = True

    def __rule_partial_update_children(self):
        """
        如果类的父类都是 PartialElement, 则取srcParent中最优先的类为其父类。
        这是为了设置合适的父类, 比如NamedElement, Classifier等等。
        """
        for child in self.classes.values():
            self.__rule_partial_update_children_gen(child)

    def __rule_partial_update_children_gen(self, child: model.UmlClass):
        if child.name in UmlTransform.partial_classes.keys():
            return
        if len(child.generalizations) == 0:
            return
        parents = []
        for general in child.generalizations:
            if general.general not in UmlTransform.partial_classes.keys():
                return
            src_parent = UmlTransform.partial_classes[general.general].get(
                "srcParent", None
            )
            if src_parent is not None:
                parents.append(src_parent)

        if len(parents) == 0:
            return

        # 最优先的类为其父类
        for parent in UmlTransform.__parent_order:
            if parent in parents:
                generalization = model.UmlGeneralization()
                generalization.general = parent
                child.generalizations.insert(0, generalization)

    def __calc_is_partial_element(self):
        for child in self.classes.values():
            self.__calc_is_partial_element_each(child)

    def __calc_is_partial_element_each(self, cls: model.UmlClass) -> bool:
        if cls.isPartialElement is not None:
            return cls.isPartialElement

        if len(cls.generalizations) == 0:
            cls.baseClass = self.__get_base_class(cls)
            cls.isPartialElement = False
            return cls.isPartialElement

        for general in cls.generalizations:
            parent_name = self.convert_href_type(general.general)
            parent = self.classes.get(parent_name)
            is_parent_partial = self.__calc_is_partial_element_each(parent)
            if not is_parent_partial:
                cls.baseClass = parent_name
                cls.isPartialElement = False
                return cls.isPartialElement

        cls.baseClass = self.convert_href_type(cls.generalizations[0].general)
        cls.isPartialElement = True
        return cls.isPartialElement

    def __get_base_class(self, cls: model.UmlClass) -> str:
        if cls.name in UmlTransform.base_element_classes:
            generalization = model.UmlGeneralization()
            generalization.general = UmlTransform.base_class
            cls.generalizations.append(generalization)
            return UmlTransform.base_class

        if len(cls.generalizations) == 0:
            return UmlTransform.json_class

        return ""

    def __rule_replace_parent(self):
        for name in UmlTransform.__rule_replace_parent_classes:
            self.__rule_replace_parent_each(name)

    def __rule_replace_parent_each(self, name: str):
        """
        首个父类的所有成员, 如果本类没有全部加入本类。
        不继承首个父类

        Args:
            name (str): class name
        """
        cls: model.UmlClass = self.classes[name]
        p_cls = self.classes[self.convert_href_type(cls.generalizations[0].general)]

        while p_cls is not None:
            for attr in p_cls.ownedAttributes:
                if not any(attr.name == a.name for a in cls.ownedAttributes):
                    cls.ownedAttributes.append(attr)
                else:
                    attr.redefinedProperty = ""

            for op in p_cls.ownedOperations:
                if not any(op.name == o.name for o in cls.ownedOperations):
                    cls.ownedOperations.append(op)
                else:
                    op.redefinedOperation = ""

            if p_cls.generalizations:
                p_cls = self.classes[
                    self.convert_href_type(p_cls.generalizations[0].general)
                ]
                if p_cls.name == "DiagramElement":
                    break
            else:
                p_cls = None
        cls.generalizations.pop(0)

    def convert_href_type(self, type_name: str, with_path: bool = False) -> str:
        for key, value in UmlTransform.href_types.items():
            if with_path:
                # for include statements
                type_name = type_name.replace(key, value)
            else:
                type_name = type_name.replace(key, "")

        return type_name
